﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using Newtonsoft.Json;

namespace Imgur.Net.Auth
{
    public class RegisteredAuthenticationProvider : ImgurAuthProvider
    {
        public event EventHandler<RegistrationTokensChangedEventArgs> RegistrationTokensChanged;

        /// <summary>
        /// Current Client ID.
        /// </summary>
        public string ClientId { get; private set; }

        /// <summary>
        /// ClientSecret code used when authorizing registered users.
        /// </summary>
        public string ClientSecret { get; private set; }

        private RegisteredUserAuthContext CurrentAuthenticationContext { get; set; }

        public Func<Uri, RegistrationTokenContext> CredentialsFunction { get; private set; }

        private static AuthUtils authUtils = new AuthUtils();

        private static readonly string DefaultTokenEndpointTemplate = "https://api.imgur.com/oauth2/token";
        private static readonly string AuthorizeEndpointTemplate = "https://api.imgur.com/oauth2/authorize?client_id={0}&response_type=token";

        private readonly object _lock = new object();

        public RegisteredAuthenticationProvider(
            string clientId, string clientSecret, RegistrationTokenContext currentTokens,
            Func<Uri, RegistrationTokenContext> credentialsFunction) :
                this(clientId, clientSecret, currentTokens, credentialsFunction, DefaultTokenEndpointTemplate)
        {
            
        }

        public RegisteredAuthenticationProvider(
            string clientId, string clientSecret, RegistrationTokenContext currentTokens,
            Func<Uri, RegistrationTokenContext> credentialsFunction, string tokenEndpoint)
        {
            if (string.IsNullOrEmpty(clientId))
                throw new ArgumentNullException("clientId");

            if (string.IsNullOrEmpty(clientSecret))
                throw new ArgumentNullException("clientSecret");

            if (currentTokens == null)
                throw new ArgumentNullException("currentTokens");

            if (credentialsFunction == null)
                throw new ArgumentNullException("credentialsFunction");

            if (string.IsNullOrEmpty(tokenEndpoint))
                throw new ArgumentNullException("tokenEndpoint");

            ClientId = clientId;
            ClientSecret = clientSecret;
            CurrentAuthenticationContext = new RegisteredUserAuthContext(ClientId, currentTokens);
            CredentialsFunction = credentialsFunction;
            TokenEndpoint = tokenEndpoint;
        }

        public string TokenEndpoint { get; private set; }

        protected virtual void FireRegistrationTokensChanged()
        {
            var handler = RegistrationTokensChanged;
            if (handler != null)
                handler(this, new RegistrationTokensChangedEventArgs( CurrentAuthenticationContext.RegistrationContext ));
        }

        public ImgurAuthContext GetAuthorizationContext()
        {
            lock (_lock)
            {
                // check state
                switch (
                    authUtils.CheckRegisteredAuthorizationTokenState(CurrentAuthenticationContext.RegistrationContext))
                {
                    case RegisteredAuthorizationState.AuthorizationExists:
                        break;
                    case RegisteredAuthorizationState.RequiresLoginNoRefreshToken:
                        GetAuthenticationContextViaLogin();
                        break;
                    case RegisteredAuthorizationState.RequiresLoginExpired:
                    case RegisteredAuthorizationState.RequiresLoginNoAccessToken:
                        GetAuthenticationContextViaRefresh();
                        break;

                }
            }

            return CurrentAuthenticationContext;
        }

        private void GetAuthenticationContextViaRefresh()
        {
            string requestUrl = TokenEndpoint;

            WebRequest hr = WebRequest.Create(requestUrl);

            // set the client ID header
            hr.Headers.Add("Authorization", string.Format("Client-ID {0}", ClientId));

            hr.Method = "POST";

            string boundary = "-------------------------------------" + DateTime.Now.Ticks;
            string newLine = Environment.NewLine;
            string kvPair = "--" + boundary + newLine +
                            "Content-Disposition: form-data; name=\"{0}\"" + newLine + newLine + "{1}" + newLine;

            hr.ContentType = "multipart/form-data; boundary=" + boundary;

            // set post parameters
            using (Stream s = hr.GetRequestStream())
            {
                using (StreamWriter sw = new StreamWriter(s))
                {
                    string refreshParam = string.Format(kvPair, "refresh_token",
                        CurrentAuthenticationContext.RegistrationContext.RefreshToken);
                    sw.Write(refreshParam);

                    string clientIdParam = string.Format(kvPair, "client_id", ClientId);
                    sw.Write(clientIdParam);
                    
                    string clientSecretParam = string.Format(kvPair, "client_secret", ClientSecret);
                    sw.Write(clientSecretParam);
                    
                    string grantTypeParam = string.Format(kvPair, "grant_type", "refresh_token");
                    sw.Write(grantTypeParam);
                    
                    sw.Write(boundary);

                    sw.Flush();
                    sw.Close();
                }
            }

            string responseString = string.Empty;

            try
            {
                using (HttpWebResponse response = (HttpWebResponse)hr.GetResponse())
                {
                    if (response.StatusCode != HttpStatusCode.OK)
                        throw new RefreshTokenInvalidException();

                    using (Stream responseStream = response.GetResponseStream())
                    {
                        using (StreamReader sReader = new StreamReader(responseStream))
                        {
                            responseString = sReader.ReadToEnd();
                        }
                    }
                }

            }
            catch (WebException wex)
            {
                responseString = new StreamReader(wex.Response.GetResponseStream()).ReadToEnd();
            }

            RefreshTokenResponse jsonResponse = JsonConvert.DeserializeObject<RefreshTokenResponse>(responseString);

            var registrationTokens = new DefaultRegistrationTokenContext(jsonResponse.AccessToken, jsonResponse.RefreshToken,
                DateTime.Now.AddSeconds(jsonResponse.ExpiresIn));

            CurrentAuthenticationContext = new RegisteredUserAuthContext(ClientId, registrationTokens);
            FireRegistrationTokensChanged();
        }

        private void GetAuthenticationContextViaLogin()
        {
            Uri authorizationUri = new Uri(string.Format(AuthorizeEndpointTemplate, ClientId));
            RegistrationTokenContext tokens = CredentialsFunction(authorizationUri);
            CurrentAuthenticationContext = new RegisteredUserAuthContext(ClientId, tokens);
            FireRegistrationTokensChanged();
        }
    }
}
